#!/bin/sh /etc/rc.common
#
# Copyright (C) 2014-2017 Jian Chang <aa65535@live.com>
#
# This is free software, licensed under the GNU General Public License v3.
# See /LICENSE for more information.
#

START=90
STOP=15

NAME=shadowsocks
EXTRA_COMMANDS=rules

uci_get_by_name() {
	local ret=$(uci get $NAME.$1.$2 2>/dev/null)
	echo ${ret:=$3}
}

uci_get_by_type() {
	local ret=$(uci get $NAME.@$1[0].$2 2>/dev/null)
	echo ${ret:=$3}
}

uci_bool_by_name() {
	case "$(uci_get_by_name $1 $2)" in
		1|on|true|yes|enabled) return 0;;
	esac
	return 1
}

validate_server() {
	[ "$(uci get $NAME.$1 2>/dev/null)" = "servers" ]
}

has_valid_server() {
	for server in $@; do
		validate_server $server && return 0
	done
	return 1
}

get_arg_udp() {
	local server=$(uci_get_by_type transparent_proxy udp_relay_server)
	[ "$server" = "same" ] || validate_server $server && echo "-u"
}

get_arg_out() {
	case "$(uci_get_by_type access_control self_proxy 1)" in
		1) echo "-o";;
		2) echo "-O";;
	esac
}

get_arg_tfo() {
	uci_bool_by_name $1 fast_open && echo "--fast-open"
}

get_server_ips() {
	echo $(uci_get_by_name $1 server)
}

get_lan_hosts() {
	uci_bool_by_name $1 enable && \
		echo "$(uci_get_by_name $1 type),$(uci_get_by_name $1 macaddr)"
}

get_plugin_config() {
	local plugin=$(uci_get_by_name $1 plugin)
	local plugin_opts=$(uci_get_by_name $1 plugin_opts)
	if [ -n "$plugin" ]; then
		echo $plugin >>/var/run/ss-plugin
		echo -e  "\n    \"plugin\": \"$plugin\","
		if [ -n "$plugin_opts" ]; then
			echo "    \"plugin_opts\": \"$plugin_opts\","
		fi
	fi
}

get_crypto_config() {
	local key=$(uci_get_by_name $1 key)
	local password=$(uci_get_by_name $1 password)
	if [ -n "$key" ]; then
		echo "\"key\": \"$key\","
	elif [ -n "$password" ]; then
		echo "\"password\": \"$password\","
	else
		logger -st $NAME -p3 "The password or key is not set."
	fi
}

gen_config_file() {
	local config_file=/var/etc/$NAME.$1.json
	cat <<-EOF >$config_file
		{
		    "server": "$(uci_get_by_name $1 server)",
		    "server_port": $(uci_get_by_name $1 server_port),
		    $(get_crypto_config $1)
		    "method": "$(uci_get_by_name $1 encrypt_method)",
		    "local_address": "0.0.0.0",$(get_plugin_config $1)
		    "timeout": $(uci_get_by_name $1 timeout 60),
		    "reuse_port": true
		}
EOF
	echo $config_file
}

start_rules() {
	config_load $NAME
	/usr/bin/ss-rules \
		-s "$(config_foreach get_server_ips servers)" \
		-l "$(uci_get_by_type transparent_proxy local_port 1234)" \
		-B "$(uci_get_by_type access_control wan_bp_list)" \
		-b "$(uci_get_by_type access_control wan_bp_ips)" \
		-W "$(uci_get_by_type access_control wan_fw_list)" \
		-w "$(uci_get_by_type access_control wan_fw_ips)" \
		-I "$(uci_get_by_type access_control lan_ifaces)" \
		-d "$(uci_get_by_type access_control lan_target)" \
		-a "$(config_foreach get_lan_hosts lan_hosts)" \
		-e "$(uci_get_by_type access_control ipt_ext)" \
		$(get_arg_out) $(get_arg_udp)
}

rules() {
	pidof ss-redir >/dev/null || return 0
	start_rules || /usr/bin/ss-rules -f
}

start_redir() {
	validate_server $1 || return 0
	ss-redir -c $(gen_config_file $1) $2 $(get_arg_tfo $1) \
		-l $(uci_get_by_type transparent_proxy local_port 1234) \
		--mtu $(uci_get_by_type transparent_proxy mtu 1492) \
		-f /var/run/ss-redir$3-$1.pid
}

ss_redir() {
	command -v ss-redir >/dev/null 2>&1 || return 1
	local main_server=$(uci_get_by_type transparent_proxy main_server)
	has_valid_server $main_server || return 1
	local udp_relay_server=$(uci_get_by_type transparent_proxy udp_relay_server)
	if [ "$udp_relay_server" = "same" ]; then
		for server in $main_server; do
			start_redir $server -u
		done
	else
		for server in $main_server; do
			start_redir $server
		done
		for server in $udp_relay_server; do
			start_redir $server -U -udp
		done
	fi
}

start_local() {
	validate_server $1 || return 0
	ss-local -c $(gen_config_file $1) -u $(get_arg_tfo $1) \
		-l $(uci_get_by_type socks5_proxy local_port 1080) \
		--mtu $(uci_get_by_type socks5_proxy mtu 1492) \
		-f /var/run/ss-local-$1.pid
}

ss_local() {
	command -v ss-local >/dev/null 2>&1 || return 0
	for server in $(uci_get_by_type socks5_proxy server); do
		start_local $server
	done
}

start_tunnel() {
	validate_server $1 || return 0
	ss-tunnel -c $(gen_config_file $1) -u \
		-l $(uci_get_by_type port_forward local_port 5300) \
		-L $(uci_get_by_type port_forward destination 8.8.4.4:53) \
		--mtu $(uci_get_by_type port_forward mtu 1492) \
		-f /var/run/ss-tunnel-$1.pid
}

ss_tunnel() {
	command -v ss-tunnel >/dev/null 2>&1 || return 0
	for server in $(uci_get_by_type port_forward server); do
		start_tunnel $server
	done
}

start() {
	mkdir -p /var/run /var/etc
	ss_redir && rules
	ss_local
	ss_tunnel
}

boot() {
	local delay=$(uci_get_by_type general startup_delay 0)
	(sleep $delay && start >/dev/null 2>&1) &
	return 0
}

kill_all() {
	kill -9 $(pidof $@) >/dev/null 2>&1
}

stop() {
	/usr/bin/ss-rules -f
	kill_all ss-redir ss-local ss-tunnel
	if [ -f /var/run/ss-plugin ]; then
		kill_all $(sort -u /var/run/ss-plugin)
		rm -f /var/run/ss-plugin
	fi
}
